﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using Xejen.Database.Entities;
using Xejen.Database.Exceptions;

namespace Xejen.Database.Dapper
{
    /// <inheritdoc cref="IRepository{TEntity, TPrimaryKey}"/>
    public abstract class RepositoryBase<TEntity, TPrimaryKey> : RepositoryBase, IRepository<TEntity, TPrimaryKey>
        where TPrimaryKey : struct
        where TEntity : class, IEntity<TPrimaryKey>
    {
        /// <inheritdoc cref="RepositoryBase{TEntity, TPrimaryKey}"/>
        protected RepositoryBase()
        {

        }

        /// <inheritdoc/>
        public abstract IQueryable<TEntity> GetAll(string sql);

        /// <inheritdoc/>
        public abstract Task<IQueryable<TEntity>> GetAllAsync(string sql);

        /// <inheritdoc/>
        public virtual List<TEntity> GetAllList(string sql)
        {
            return GetAll(sql).ToList();
        }

        /// <inheritdoc/>
        public virtual async Task<List<TEntity>> GetAllListAsync(string sql)
        {
            return (await GetAllAsync(sql)).ToList();
        }

        /// <inheritdoc/>
        public abstract TEntity Single(string sql);

        /// <inheritdoc/>
        public abstract Task<TEntity> SingleAsync(string sql);

        /// <inheritdoc/>
        public abstract TEntity SingleOrDefault(string sql);

        /// <inheritdoc/>
        public abstract Task<TEntity> SingleOrDefaultAsync(string sql);

        /// <inheritdoc/>
        public abstract TEntity FirstOrDefault(string sql);

        /// <inheritdoc/>
        public abstract Task<TEntity> FirstOrDefaultAsync(string sql);

        /// <inheritdoc/>
        public abstract TEntity Insert(string sql, TEntity entity);

        /// <inheritdoc/>
        public abstract Task<TEntity> InsertAsync(string sql, TEntity entity);

        /// <inheritdoc/>
        public abstract int Insert(string sql, List<TEntity> entities);

        /// <inheritdoc/>
        public abstract Task<int> InsertAsync(string sql, List<TEntity> entities);

        /// <inheritdoc/>
        public virtual TPrimaryKey InsertAndGetId(string sql, TEntity entity)
        {
            return Insert(sql, entity).Id;
        }

        /// <inheritdoc/>
        public virtual async Task<TPrimaryKey> InsertAndGetIdAsync(string sql, TEntity entity)
        {
            var insertedEntity = await InsertAsync(sql, entity);
            return insertedEntity.Id;
        }

        /// <inheritdoc/>
        public virtual TEntity InsertOrUpdate(string sql, TEntity entity)
        {
            return entity.IsTransient()
                ? Insert(sql, entity)
                : Update(sql, entity);
        }

        /// <inheritdoc/>
        public virtual async Task<TEntity> InsertOrUpdateAsync(string sql, TEntity entity)
        {
            return entity.IsTransient()
                ? await InsertAsync(sql, entity)
                : await UpdateAsync(sql, entity);
        }

        /// <inheritdoc/>
        public virtual TPrimaryKey InsertOrUpdateAndGetId(string sql, TEntity entity)
        {
            return InsertOrUpdate(sql, entity).Id;
        }

        /// <inheritdoc/>
        public virtual async Task<TPrimaryKey> InsertOrUpdateAndGetIdAsync(string sql, TEntity entity)
        {
            var insertedEntity = await InsertOrUpdateAsync(sql, entity);
            return insertedEntity.Id;
        }

        /// <inheritdoc/>
        public abstract TEntity Update(string sql, TEntity entity);

        /// <inheritdoc/>
        public abstract Task<TEntity> UpdateAsync(string sql, TEntity entity);

        /// <inheritdoc/>
        public abstract Task<int> UpdateAsync(string sql, List<TEntity> entities);

        /// <inheritdoc/>
        public abstract void Delete(string sql, TEntity entity);

        /// <inheritdoc/>
        public virtual Task DeleteAsync(string sql, TEntity entity)
        {
            Delete(sql, entity);
            return Task.CompletedTask;
        }

        /// <inheritdoc/>
        public abstract void Delete(string sql, TPrimaryKey id);

        /// <inheritdoc/>
        public virtual Task DeleteAsync(string sql, TPrimaryKey id)
        {
            Delete(sql, id);
            return Task.CompletedTask;
        }

        /// <inheritdoc/>
        public abstract int Delete(string sql, List<TEntity> entities);

        /// <inheritdoc/>
        public abstract Task<int> DeleteAsync(string sql, List<TEntity> entities);
    }
}
